{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fa7e3e52",
   "metadata": {},
   "source": [
    "# ResNet50图像分类\n",
    "\n",
    "[![下载Notebook](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.3/resource/_static/logo_notebook.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/r2.3/tutorials/application/zh_cn/cv/mindspore_resnet50.ipynb)&emsp;[![下载样例代码](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.3/resource/_static/logo_download_code.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/r2.3/tutorials/application/zh_cn/cv/mindspore_resnet50.py)&emsp;[![查看源文件](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.3/resource/_static/logo_source.svg)](https://gitee.com/mindspore/docs/blob/r2.3/tutorials/application/source_zh_cn/cv/resnet50.ipynb)\n",
    "\n",
    "图像分类是最基础的计算机视觉应用，属于有监督学习类别，如给定一张图像(猫、狗、飞机、汽车等等)，判断图像所属的类别。本章将介绍使用ResNet50网络对CIFAR-10数据集进行分类。\n",
    "\n",
    "## ResNet网络介绍\n",
    "\n",
    "ResNet50网络是2015年由微软实验室的何恺明提出，获得ILSVRC2015图像分类竞赛第一名。在ResNet网络提出之前，传统的卷积神经网络都是将一系列的卷积层和池化层堆叠得到的，但当网络堆叠到一定深度时，就会出现退化问题。下图是在CIFAR-10数据集上使用56层网络与20层网络训练误差和测试误差图，由图中数据可以看出，56层网络比20层网络训练误差和测试误差更大，随着网络的加深，其误差并没有如预想的一样减小。\n",
    "\n",
    "![resnet-1](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.3/tutorials/application/source_zh_cn/cv/images/resnet_1.png)\n",
    "\n",
    "ResNet网络提出了残差网络结构(Residual Network)来减轻退化问题，使用ResNet网络可以实现搭建较深的网络结构（突破1000层）。论文中使用ResNet网络在CIFAR-10数据集上的训练误差与测试误差图如下图所示，图中虚线表示训练误差，实线表示测试误差。由图中数据可以看出，ResNet网络层数越深，其训练误差和测试误差越小。\n",
    "\n",
    "![resnet-4](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.3/tutorials/application/source_zh_cn/cv/images/resnet_4.png)\n",
    "\n",
    "> 了解ResNet网络更多详细内容，参见[ResNet论文](https://arxiv.org/pdf/1512.03385.pdf)。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a987ee48",
   "metadata": {},
   "source": [
    "## 数据集准备与加载\n",
    "\n",
    "[CIFAR-10数据集](http://www.cs.toronto.edu/~kriz/cifar.html)共有60000张32*32的彩色图像，分为10个类别，每类有6000张图，数据集一共有50000张训练图片和10000张评估图片。首先，如下示例使用`download`接口下载并解压，目前仅支持解析二进制版本的CIFAR-10文件（CIFAR-10 binary version）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "77ea7f0cdf0c2083",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "%%capture captured_output\n",
    "# 实验环境已经预装了mindspore==2.3.0，如需更换mindspore版本，可更改下面 MINDSPORE_VERSION 变量\n",
    "!pip uninstall mindspore -y\n",
    "!export MINDSPORE_VERSION=2.3.0\n",
    "!pip install https://ms-release.obs.cn-north-4.myhuaweicloud.com/${MINDSPORE_VERSION}/MindSpore/unified/aarch64/mindspore-${MINDSPORE_VERSION}-cp39-cp39-linux_aarch64.whl --trusted-host ms-release.obs.cn-north-4.myhuaweicloud.com -i https://pypi.mirrors.ustc.edu.cn/simple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "68358b0ca227fa68",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Name: mindspore\n",
      "Version: 2.3.0\n",
      "Summary: MindSpore is a new open source deep learning training/inference framework that could be used for mobile, edge and cloud scenarios.\n",
      "Home-page: https://www.mindspore.cn\n",
      "Author: The MindSpore Authors\n",
      "Author-email: contact@mindspore.cn\n",
      "License: Apache 2.0\n",
      "Location: /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages\n",
      "Requires: asttokens, astunparse, numpy, packaging, pillow, protobuf, psutil, scipy\n",
      "Required-by: \n"
     ]
    }
   ],
   "source": [
    "# 查看当前 mindspore 版本\n",
    "!pip show mindspore"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "1f9b81fb",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Creating data folder...\n",
      "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz (162.2 MB)\n",
      "\n",
      "file_sizes: 100%|█████████████████████████████| 170M/170M [00:01<00:00, 159MB/s]\n",
      "Extracting tar.gz file...\n",
      "Successfully downloaded / unzipped to ./datasets-cifar10-bin\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'./datasets-cifar10-bin'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from download import download\n",
    "\n",
    "url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz\"\n",
    "\n",
    "download(url, \"./datasets-cifar10-bin\", kind=\"tar.gz\", replace=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e9020ba",
   "metadata": {},
   "source": [
    "下载后的数据集目录结构如下：\n",
    "\n",
    "```text\n",
    "datasets-cifar10-bin/cifar-10-batches-bin\n",
    "├── batches.meta.text\n",
    "├── data_batch_1.bin\n",
    "├── data_batch_2.bin\n",
    "├── data_batch_3.bin\n",
    "├── data_batch_4.bin\n",
    "├── data_batch_5.bin\n",
    "├── readme.html\n",
    "└── test_batch.bin\n",
    "\n",
    "```\n",
    "\n",
    "然后，使用`mindspore.dataset.Cifar10Dataset`接口来加载数据集，并进行相关图像增强操作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "df7fb621",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:549: UserWarning: The value of the smallest subnormal for <class 'numpy.float64'> type is zero.\n",
      "  setattr(self, word, getattr(machar, word).flat[0])\n",
      "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:89: UserWarning: The value of the smallest subnormal for <class 'numpy.float64'> type is zero.\n",
      "  return self._float_to_str(self.smallest_subnormal)\n",
      "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:549: UserWarning: The value of the smallest subnormal for <class 'numpy.float32'> type is zero.\n",
      "  setattr(self, word, getattr(machar, word).flat[0])\n",
      "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:89: UserWarning: The value of the smallest subnormal for <class 'numpy.float32'> type is zero.\n",
      "  return self._float_to_str(self.smallest_subnormal)\n"
     ]
    }
   ],
   "source": [
    "import mindspore as ms\n",
    "import mindspore.dataset as ds\n",
    "import mindspore.dataset.vision as vision\n",
    "import mindspore.dataset.transforms as transforms\n",
    "from mindspore import dtype as mstype\n",
    "\n",
    "data_dir = \"./datasets-cifar10-bin/cifar-10-batches-bin\"  # 数据集根目录\n",
    "batch_size = 256  # 批量大小\n",
    "image_size = 32  # 训练图像空间大小\n",
    "workers = 4  # 并行线程个数\n",
    "num_classes = 10  # 分类数量\n",
    "\n",
    "\n",
    "def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers):\n",
    "\n",
    "    data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir,\n",
    "                                 usage=usage,\n",
    "                                 num_parallel_workers=workers,\n",
    "                                 shuffle=True)\n",
    "\n",
    "    trans = []\n",
    "    if usage == \"train\":\n",
    "        trans += [\n",
    "            vision.RandomCrop((32, 32), (4, 4, 4, 4)),\n",
    "            vision.RandomHorizontalFlip(prob=0.5)\n",
    "        ]\n",
    "\n",
    "    trans += [\n",
    "        vision.Resize(resize),\n",
    "        vision.Rescale(1.0 / 255.0, 0.0),\n",
    "        vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),\n",
    "        vision.HWC2CHW()\n",
    "    ]\n",
    "\n",
    "    target_trans = transforms.TypeCast(mstype.int32)\n",
    "\n",
    "    # 数据映射操作\n",
    "    data_set = data_set.map(operations=trans,\n",
    "                            input_columns='image',\n",
    "                            num_parallel_workers=workers)\n",
    "\n",
    "    data_set = data_set.map(operations=target_trans,\n",
    "                            input_columns='label',\n",
    "                            num_parallel_workers=workers)\n",
    "\n",
    "    # 批量操作\n",
    "    data_set = data_set.batch(batch_size)\n",
    "\n",
    "    return data_set\n",
    "\n",
    "\n",
    "# 获取处理后的训练与测试数据集\n",
    "\n",
    "dataset_train = create_dataset_cifar10(dataset_dir=data_dir,\n",
    "                                       usage=\"train\",\n",
    "                                       resize=image_size,\n",
    "                                       batch_size=batch_size,\n",
    "                                       workers=workers)\n",
    "step_size_train = dataset_train.get_dataset_size()\n",
    "\n",
    "dataset_val = create_dataset_cifar10(dataset_dir=data_dir,\n",
    "                                     usage=\"test\",\n",
    "                                     resize=image_size,\n",
    "                                     batch_size=batch_size,\n",
    "                                     workers=workers)\n",
    "step_size_val = dataset_val.get_dataset_size()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21e86f95",
   "metadata": {},
   "source": [
    "对CIFAR-10训练数据集进行可视化。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c3ffabb3",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Image shape: (256, 3, 32, 32), Label shape: (256,)\n",
      "Labels: [6 1 0 7 5 9]\n"
     ]
    },
    {
     "data": {
      "image/png": "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09133632mc1mzcn1oosuoieeeIIef/xxto+vfe1rNDg4SMcdd9yruRR2HKLf/GXDb/OFL3yBiEj9VczevXvZXxhMTk7SP/3TP9Hq1atpzpw5DR/3qquuIt/36VOf+pT6t3q9/pp84IBXR6Pj/XA4nHF08Jv+b59TGIb0D//wD4d9HlEZh/hl4FVw6aWX0tlnn02f+MQnaPv27XTiiSfS97//ffq3f/s3+tCHPjQz4a9Zs4be+c530he/+EUaHR2d+dPCg6vcI/3zK3j9uOSSS+if//mfqaWlhY477jh6/PHH6Qc/+AF1dHSwuPPPP5/mz59P733ve+kjH/kIeZ5HX//616mrq4t27tzJYtesWUNf+cpX6NOf/jQtXryYuru76ZxzzqGPfvSj9M1vfpMuvPBCuvHGG6m9vZ3uvvtu2rZtG337299+zdJaJ554Il1zzTX0ta99jfL5PK1du5aeeOIJuvvuu+nyyy+ns88+m8UvXbqU3vve99KTTz5JPT099PWvf52GhobozjvvfEXHXbt2Ld1www1022230TPPPEPnn38+xeNx2rRpE9133330D//wD3TFFVe8JtcIDo9Gx/vhcDjjaPny5bRo0SL68Ic/THv27KHm5mb69re//ar8ACIzDmfrzxjeqLzcnxZms1kzfmpqKvzLv/zLsLe3N4zH4+GSJUvCz372s2EQBCxueno6/LM/+7Owvb09zOVy4eWXXx5u2LAhJKLwv/23//a6XhM4coyPj4fXXXdd2NnZGeZyufCCCy4IX3rppXBgYCC85pprWOxTTz0VnnbaaWEikQjnz58ffuELXzD/tHD//v3hxRdfHDY1NYVExP7McMuWLeEVV1wRtra2hqlUKjz11FPD7373u+w41pgOw//4M8Ynn3yS9R/8s8fh4eGZvlqtFt56663hggULwng8Hvb394cf+9jHwnK5zLYdGBgIL7744vChhx4KTzjhhDCZTIbLly9Xx27kTwsP8rWvfS1cs2ZNmE6nw6ampvD4448P//qv/zrcu3evigVHlkbH+8v9aeHKlSvN/b6acbR+/frw7W9/e5jL5cLOzs7wT/7kT8Jnn302JKLwzjvvnIl7uXn95f7s91gfh04Yit94wBHjmWeeoZNOOom+8Y1vzDgaAgBA1BkcHKRVq1bRd7/73dk+lcgAzcARQlphEv0mB+u6Lp15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An4LLAYAAAAcMR577DG69dZbsRh4g4HFwOtAGIZUKpVm+zQAAOCoJQgCKpfLs30akSHSi4F8Pk/XXnsttba2UktLC1133XVULBZn/r1er9OnPvUpWrRoESWTSRocHKSPf/zjVKlU2H4GBwfpkksuoYceeoje9KY3UTqdpv/xP/4HERE9/PDDdMYZZ1BrayvlcjlatmwZffzjH2fbVyoVuvnmm2nx4sWUTCapv7+f/vqv/1odB4BG+NnPfkannHIKpVIpWrRo0cxY/G0aHdtBENAtt9xCvb29lMlk6Oyzz6b169fT4OAgXXvttUfoisCxwi233EIf+chHiIhowYIF5DgOOY5D27dvJ8dx6M///M/pnnvuoZUrV1IymaQHH3yQfvzjH5PjOPTjH/+Y7evgNnfddRfrf+mll+iqq66irq4uSqfTtGzZMvrEJz7xO89rx44dtHjxYlq1ahUNDQ29lpd81BCb7ROYTa666ipasGAB3XbbbfT000/T//yf/5O6u7vp7//+74mI6Prrr6e7776brrjiCrrpppvoF7/4Bd1222304osv0r/+67+yfW3YsIHe9a530Q033EB/8id/QsuWLaMXXniBLrnkEjrhhBPok5/8JCWTSdq8eTM9+uijM9sFQUCXXXYZ/exnP6P3ve99tGLFCnruuefo9ttvp40bN9J3vvOdI3lLwFHOc889R+effz51dXXRLbfcQvV6nW6++Wbq6elhcY2O7Y997GP0mc98hi699FK64IIL6Nlnn6ULLrgA39jAYfGf/tN/oo0bN9I3v/lNuv3226mzs5OIiLq6uoiI6Ec/+hHde++99Od//ufU2dlJg4ODryid8Otf/5re9ra3UTwep/e97300ODhIW7Zsof/7f/8v/df/+l/NbbZs2ULnnHMOtbe308MPPzxzTpEjjCA333xzSEThe97zHtb/jne8I+zo6AjDMAyfeeaZkIjC66+/nsV8+MMfDoko/NGPfjTTNzAwEBJR+OCDD7LY22+/PSSicHh4+GXP5Z//+Z9D13XDn/70p6z/q1/9akhE4aOPPnpY1wiiyeWXXx6mUqlwx44dM33r168PPc8LD77ujY7t/fv3h7FYLLz88stZ3C233BISUXjNNde8vhcDjkk++9nPhkQUbtu2jfUTUei6bvjCCy+w/nXr1oVEFK5bt471b9u2LSSi8M4775zpO/PMM8OmpiY2/sMwDIMgmPnvg/P/8PBw+OKLL4a9vb3hKaecEo6Njb0m13e0Euk0wfvf/37Wftvb3kajo6M0OTlJ3/ve94iI6K/+6q9YzE033URERPfffz/rX7BgAV1wwQWsr7W1lYiI/u3f/o2CIDDP4b777qMVK1bQ8uXLaWRkZOZ/55xzDhERrVu37vAuDkQO3/fpoYceossvv5zmz58/079ixQo2Nhsd2z/84Q+pXq/TBz7wARb3wQ9+8HU5fwDWrl1Lxx133GFtOzw8TI888gi95z3vYeOfiMw/q33++edp7dq1NDg4SD/4wQ+ora3tsI57rBDpxYAcMAcHw/j4OO3YsYNc16XFixezmDlz5lBrayvt2LGD9S9YsEDt/w/+4A/orW99K11//fXU09NDV199Nd17771sYbBp0yZ64YUXqKuri/1v6dKlRER04MCB1+RawbHP8PAwlUolWrJkifq3ZcuWzfx3o2P74P/LuPb29shPnOD1wZpHG2Xr1q1ERLRq1aqG4i+99FJqamqihx56iJqbmw/7uMcKkdYMeJ5n9odhOPPfjRq1pNNps++RRx6hdevW0f33308PPvggfetb36JzzjmHvv/975PneRQEAR1//PH0hS98wdxvf39/Q8cH4JUCEyLwRsOaR19unPq+/6qO9c53vpPuvvtuuueee+iGG254Vfs6Foj0YuB3MTAwQEEQ0KZNm2jFihUz/UNDQ5TP52lgYKCh/biuS+eeey6de+659IUvfIH+7u/+jj7xiU/QunXr6O1vfzstWrSInn32WTr33HMxOYNXxUH19KZNm9S/bdiwYea/Gx3bB/9/8+bN7Bvb6OgojY+Pv16XAY5xXuk8d/BXKCkklL/OLly4kIh+8/N/I3z2s5+lWCxGH/jAB6ipqYn+8A//8BWd17FGpNMEv4uLLrqIiIi++MUvsv6D3+AvvvjiQ+5jbGxM9a1evZqIaOZPuK666iras2cP/eM//qOKLZVKND09/UpOG0QYz/PoggsuoO985zu0c+fOmf4XX3yRHnrooZl2o2P73HPPpVgsRl/5yldY3Je+9KXX4/RBRMhms0SkP9xfjoGBAfI8jx555BHW/+Uvf5m1u7q66Mwzz6Svf/3rbPwT8V97D+I4Dn3ta1+jK664gq655hr693//91dwFcce+GXgZTjxxBPpmmuu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",
      "text/plain": [
       "<Figure size 640x480 with 6 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "data_iter = next(dataset_train.create_dict_iterator())\n",
    "\n",
    "images = data_iter[\"image\"].asnumpy()\n",
    "labels = data_iter[\"label\"].asnumpy()\n",
    "print(f\"Image shape: {images.shape}, Label shape: {labels.shape}\")\n",
    "\n",
    "# 训练数据集中，前六张图片所对应的标签\n",
    "print(f\"Labels: {labels[:6]}\")\n",
    "\n",
    "classes = []\n",
    "\n",
    "with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n",
    "    for line in f:\n",
    "        line = line.rstrip()\n",
    "        if line:\n",
    "            classes.append(line)\n",
    "\n",
    "# 训练数据集的前六张图片\n",
    "plt.figure()\n",
    "for i in range(6):\n",
    "    plt.subplot(2, 3, i + 1)\n",
    "    image_trans = np.transpose(images[i], (1, 2, 0))\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465])\n",
    "    std = np.array([0.2023, 0.1994, 0.2010])\n",
    "    image_trans = std * image_trans + mean\n",
    "    image_trans = np.clip(image_trans, 0, 1)\n",
    "    plt.title(f\"{classes[labels[i]]}\")\n",
    "    plt.imshow(image_trans)\n",
    "    plt.axis(\"off\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76c96f76",
   "metadata": {},
   "source": [
    "## 构建网络\n",
    "\n",
    "残差网络结构(Residual Network)是ResNet网络的主要亮点，ResNet使用残差网络结构后可有效地减轻退化问题，实现更深的网络结构设计，提高网络的训练精度。本节首先讲述如何构建残差网络结构，然后通过堆叠残差网络来构建ResNet50网络。\n",
    "\n",
    "### 构建残差网络结构\n",
    "\n",
    "残差网络结构图如下图所示，残差网络由两个分支构成：一个主分支，一个shortcuts（图中弧线表示）。主分支通过堆叠一系列的卷积操作得到，shortcuts从输入直接到输出，主分支输出的特征矩阵$F(x)$加上shortcuts输出的特征矩阵$x$得到$F(x)+x$，通过Relu激活函数后即为残差网络最后的输出。\n",
    "\n",
    "![residual](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.3/tutorials/application/source_zh_cn/cv/images/resnet_3.png)\n",
    "\n",
    "残差网络结构主要由两种，一种是Building Block，适用于较浅的ResNet网络，如ResNet18和ResNet34；另一种是Bottleneck，适用于层数较深的ResNet网络，如ResNet50、ResNet101和ResNet152。\n",
    "\n",
    "#### Building Block\n",
    "\n",
    "Building Block结构图如下图所示，主分支有两层卷积网络结构：\n",
    "\n",
    "+ 主分支第一层网络以输入channel为64为例，首先通过一个$3\\times3$的卷积层，然后通过Batch Normalization层，最后通过Relu激活函数层，输出channel为64；\n",
    "+ 主分支第二层网络的输入channel为64，首先通过一个$3\\times3$的卷积层，然后通过Batch Normalization层，输出channel为64。\n",
    "\n",
    "最后将主分支输出的特征矩阵与shortcuts输出的特征矩阵相加，通过Relu激活函数即为Building Block最后的输出。\n",
    "\n",
    "![building-block-5](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.3/tutorials/application/source_zh_cn/cv/images/resnet_5.png)\n",
    "\n",
    "主分支与shortcuts输出的特征矩阵相加时，需要保证主分支与shortcuts输出的特征矩阵shape相同。如果主分支与shortcuts输出的特征矩阵shape不相同，如输出channel是输入channel的一倍时，shortcuts上需要使用数量与输出channel相等，大小为$1\\times1$的卷积核进行卷积操作；若输出的图像较输入图像缩小一倍，则要设置shortcuts中卷积操作中的`stride`为2，主分支第一层卷积操作的`stride`也需设置为2。\n",
    "\n",
    "如下代码定义`ResidualBlockBase`类实现Building Block结构。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c7ac0e2d",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from typing import Type, Union, List, Optional\n",
    "import mindspore.nn as nn\n",
    "from mindspore.common.initializer import Normal\n",
    "\n",
    "# 初始化卷积层与BatchNorm的参数\n",
    "weight_init = Normal(mean=0, sigma=0.02)\n",
    "gamma_init = Normal(mean=1, sigma=0.02)\n",
    "\n",
    "class ResidualBlockBase(nn.Cell):\n",
    "    expansion: int = 1  # 最后一个卷积核数量与第一个卷积核数量相等\n",
    "\n",
    "    def __init__(self, in_channel: int, out_channel: int,\n",
    "                 stride: int = 1, norm: Optional[nn.Cell] = None,\n",
    "                 down_sample: Optional[nn.Cell] = None) -> None:\n",
    "        super(ResidualBlockBase, self).__init__()\n",
    "        if not norm:\n",
    "            self.norm = nn.BatchNorm2d(out_channel)\n",
    "        else:\n",
    "            self.norm = norm\n",
    "\n",
    "        self.conv1 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=3, stride=stride,\n",
    "                               weight_init=weight_init)\n",
    "        self.conv2 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=3, weight_init=weight_init)\n",
    "        self.relu = nn.ReLU()\n",
    "        self.down_sample = down_sample\n",
    "\n",
    "    def construct(self, x):\n",
    "        \"\"\"ResidualBlockBase construct.\"\"\"\n",
    "        identity = x  # shortcuts分支\n",
    "\n",
    "        out = self.conv1(x)  # 主分支第一层：3*3卷积层\n",
    "        out = self.norm(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv2(out)  # 主分支第二层：3*3卷积层\n",
    "        out = self.norm(out)\n",
    "\n",
    "        if self.down_sample is not None:\n",
    "            identity = self.down_sample(x)\n",
    "        out += identity  # 输出为主分支与shortcuts之和\n",
    "        out = self.relu(out)\n",
    "\n",
    "        return out"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aaa15d3c",
   "metadata": {},
   "source": [
    "#### Bottleneck\n",
    "\n",
    "Bottleneck结构图如下图所示，在输入相同的情况下Bottleneck结构相对Building Block结构的参数数量更少，更适合层数较深的网络，ResNet50使用的残差结构就是Bottleneck。该结构的主分支有三层卷积结构，分别为$1\\times1$的卷积层、$3\\times3$卷积层和$1\\times1$的卷积层，其中$1\\times1$的卷积层分别起降维和升维的作用。\n",
    "\n",
    "+ 主分支第一层网络以输入channel为256为例，首先通过数量为64，大小为$1\\times1$的卷积核进行降维，然后通过Batch Normalization层，最后通过Relu激活函数层，其输出channel为64；\n",
    "+ 主分支第二层网络通过数量为64，大小为$3\\times3$的卷积核提取特征，然后通过Batch Normalization层，最后通过Relu激活函数层，其输出channel为64；\n",
    "+ 主分支第三层通过数量为256，大小$1\\times1$的卷积核进行升维，然后通过Batch Normalization层，其输出channel为256。\n",
    "\n",
    "最后将主分支输出的特征矩阵与shortcuts输出的特征矩阵相加，通过Relu激活函数即为Bottleneck最后的输出。\n",
    "\n",
    "![building-block-6](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.3/tutorials/application/source_zh_cn/cv/images/resnet_6.png)\n",
    "\n",
    "主分支与shortcuts输出的特征矩阵相加时，需要保证主分支与shortcuts输出的特征矩阵shape相同。如果主分支与shortcuts输出的特征矩阵shape不相同，如输出channel是输入channel的一倍时，shortcuts上需要使用数量与输出channel相等，大小为$1\\times1$的卷积核进行卷积操作；若输出的图像较输入图像缩小一倍，则要设置shortcuts中卷积操作中的`stride`为2，主分支第二层卷积操作的`stride`也需设置为2。\n",
    "\n",
    "如下代码定义`ResidualBlock`类实现Bottleneck结构。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "0d46f98e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class ResidualBlock(nn.Cell):\n",
    "    expansion = 4  # 最后一个卷积核的数量是第一个卷积核数量的4倍\n",
    "\n",
    "    def __init__(self, in_channel: int, out_channel: int,\n",
    "                 stride: int = 1, down_sample: Optional[nn.Cell] = None) -> None:\n",
    "        super(ResidualBlock, self).__init__()\n",
    "\n",
    "        self.conv1 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=1, weight_init=weight_init)\n",
    "        self.norm1 = nn.BatchNorm2d(out_channel)\n",
    "        self.conv2 = nn.Conv2d(out_channel, out_channel,\n",
    "                               kernel_size=3, stride=stride,\n",
    "                               weight_init=weight_init)\n",
    "        self.norm2 = nn.BatchNorm2d(out_channel)\n",
    "        self.conv3 = nn.Conv2d(out_channel, out_channel * self.expansion,\n",
    "                               kernel_size=1, weight_init=weight_init)\n",
    "        self.norm3 = nn.BatchNorm2d(out_channel * self.expansion)\n",
    "\n",
    "        self.relu = nn.ReLU()\n",
    "        self.down_sample = down_sample\n",
    "\n",
    "    def construct(self, x):\n",
    "\n",
    "        identity = x  # shortscuts分支\n",
    "\n",
    "        out = self.conv1(x)  # 主分支第一层：1*1卷积层\n",
    "        out = self.norm1(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv2(out)  # 主分支第二层：3*3卷积层\n",
    "        out = self.norm2(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv3(out)  # 主分支第三层：1*1卷积层\n",
    "        out = self.norm3(out)\n",
    "\n",
    "        if self.down_sample is not None:\n",
    "            identity = self.down_sample(x)\n",
    "\n",
    "        out += identity  # 输出为主分支与shortcuts之和\n",
    "        out = self.relu(out)\n",
    "\n",
    "        return out"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1d8dfc9",
   "metadata": {},
   "source": [
    "#### 构建ResNet50网络\n",
    "\n",
    "ResNet网络层结构如下图所示，以输入彩色图像$224\\times224$为例，首先通过数量64，卷积核大小为$7\\times7$，stride为2的卷积层conv1，该层输出图片大小为$112\\times112$，输出channel为64；然后通过一个$3\\times3$的最大下采样池化层，该层输出图片大小为$56\\times56$，输出channel为64；再堆叠4个残差网络块（conv2_x、conv3_x、conv4_x和conv5_x），此时输出图片大小为$7\\times7$，输出channel为2048；最后通过一个平均池化层、全连接层和softmax，得到分类概率。\n",
    "\n",
    "![resnet-layer](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.3/tutorials/application/source_zh_cn/cv/images/resnet_2.png)\n",
    "\n",
    "对于每个残差网络块，以ResNet50网络中的conv2_x为例，其由3个Bottleneck结构堆叠而成，每个Bottleneck输入的channel为64，输出channel为256。\n",
    "\n",
    "如下示例定义`make_layer`实现残差块的构建，其参数如下所示:\n",
    "\n",
    "+ `last_out_channel`：上一个残差网络输出的通道数。\n",
    "+ `block`：残差网络的类别，分别为`ResidualBlockBase`和`ResidualBlock`。\n",
    "+ `channel`：残差网络输入的通道数。\n",
    "+ `block_nums`：残差网络块堆叠的个数。\n",
    "+ `stride`：卷积移动的步幅。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "3dfa40a1",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def make_layer(last_out_channel, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "               channel: int, block_nums: int, stride: int = 1):\n",
    "    down_sample = None  # shortcuts分支\n",
    "\n",
    "    if stride != 1 or last_out_channel != channel * block.expansion:\n",
    "\n",
    "        down_sample = nn.SequentialCell([\n",
    "            nn.Conv2d(last_out_channel, channel * block.expansion,\n",
    "                      kernel_size=1, stride=stride, weight_init=weight_init),\n",
    "            nn.BatchNorm2d(channel * block.expansion, gamma_init=gamma_init)\n",
    "        ])\n",
    "\n",
    "    layers = []\n",
    "    layers.append(block(last_out_channel, channel, stride=stride, down_sample=down_sample))\n",
    "\n",
    "    in_channel = channel * block.expansion\n",
    "    # 堆叠残差网络\n",
    "    for _ in range(1, block_nums):\n",
    "\n",
    "        layers.append(block(in_channel, channel))\n",
    "\n",
    "    return nn.SequentialCell(layers)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67dae353",
   "metadata": {},
   "source": [
    "ResNet50网络共有5个卷积结构，一个平均池化层，一个全连接层，以CIFAR-10数据集为例：\n",
    "\n",
    "+ **conv1**：输入图片大小为$32\\times32$，输入channel为3。首先经过一个卷积核数量为64，卷积核大小为$7\\times7$，stride为2的卷积层；然后通过一个Batch Normalization层；最后通过Reul激活函数。该层输出feature map大小为$16\\times16$，输出channel为64。\n",
    "+ **conv2_x**：输入feature map大小为$16\\times16$，输入channel为64。首先经过一个卷积核大小为$3\\times3$，stride为2的最大下采样池化操作；然后堆叠3个$[1\\times1，64；3\\times3，64；1\\times1，256]$结构的Bottleneck。该层输出feature map大小为$8\\times8$，输出channel为256。\n",
    "+ **conv3_x**：输入feature map大小为$8\\times8$，输入channel为256。该层堆叠4个[1×1，128；3×3，128；1×1，512]结构的Bottleneck。该层输出feature map大小为$4\\times4$，输出channel为512。\n",
    "+ **conv4_x**：输入feature map大小为$4\\times4$，输入channel为512。该层堆叠6个[1×1，256；3×3，256；1×1，1024]结构的Bottleneck。该层输出feature map大小为$2\\times2$，输出channel为1024。\n",
    "+ **conv5_x**：输入feature map大小为$2\\times2$，输入channel为1024。该层堆叠3个[1×1，512；3×3，512；1×1，2048]结构的Bottleneck。该层输出feature map大小为$1\\times1$，输出channel为2048。\n",
    "+ **average pool & fc**：输入channel为2048，输出channel为分类的类别数。\n",
    "\n",
    "如下示例代码实现ResNet50模型的构建，通过用调函数`resnet50`即可构建ResNet50模型，函数`resnet50`参数如下：\n",
    "\n",
    "+ `num_classes`：分类的类别数，默认类别数为1000。\n",
    "+ `pretrained`：下载对应的训练模型，并加载预训练模型中的参数到网络中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "1ebef3d0",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from mindspore import load_checkpoint, load_param_into_net\n",
    "\n",
    "\n",
    "class ResNet(nn.Cell):\n",
    "    def __init__(self, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "                 layer_nums: List[int], num_classes: int, input_channel: int) -> None:\n",
    "        super(ResNet, self).__init__()\n",
    "\n",
    "        self.relu = nn.ReLU()\n",
    "        # 第一个卷积层，输入channel为3（彩色图像），输出channel为64\n",
    "        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, weight_init=weight_init)\n",
    "        self.norm = nn.BatchNorm2d(64)\n",
    "        # 最大池化层，缩小图片的尺寸\n",
    "        self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')\n",
    "        # 各个残差网络结构块定义\n",
    "        self.layer1 = make_layer(64, block, 64, layer_nums[0])\n",
    "        self.layer2 = make_layer(64 * block.expansion, block, 128, layer_nums[1], stride=2)\n",
    "        self.layer3 = make_layer(128 * block.expansion, block, 256, layer_nums[2], stride=2)\n",
    "        self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)\n",
    "        # 平均池化层\n",
    "        self.avg_pool = nn.AvgPool2d()\n",
    "        # flattern层\n",
    "        self.flatten = nn.Flatten()\n",
    "        # 全连接层\n",
    "        self.fc = nn.Dense(in_channels=input_channel, out_channels=num_classes)\n",
    "\n",
    "    def construct(self, x):\n",
    "\n",
    "        x = self.conv1(x)\n",
    "        x = self.norm(x)\n",
    "        x = self.relu(x)\n",
    "        x = self.max_pool(x)\n",
    "\n",
    "        x = self.layer1(x)\n",
    "        x = self.layer2(x)\n",
    "        x = self.layer3(x)\n",
    "        x = self.layer4(x)\n",
    "\n",
    "        x = self.avg_pool(x)\n",
    "        x = self.flatten(x)\n",
    "        x = self.fc(x)\n",
    "\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "d16e658e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "            layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,\n",
    "            input_channel: int):\n",
    "    model = ResNet(block, layers, num_classes, input_channel)\n",
    "\n",
    "    if pretrained:\n",
    "        # 加载预训练模型\n",
    "        download(url=model_url, path=pretrained_ckpt, replace=True)\n",
    "        param_dict = load_checkpoint(pretrained_ckpt)\n",
    "        load_param_into_net(model, param_dict)\n",
    "\n",
    "    return model\n",
    "\n",
    "\n",
    "def resnet50(num_classes: int = 1000, pretrained: bool = False):\n",
    "    \"\"\"ResNet50模型\"\"\"\n",
    "    resnet50_url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt\"\n",
    "    resnet50_ckpt = \"./LoadPretrainedModel/resnet50_224_new.ckpt\"\n",
    "    return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,\n",
    "                   pretrained, resnet50_ckpt, 2048)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d40bd05a",
   "metadata": {},
   "source": [
    "## 模型训练与评估\n",
    "\n",
    "本节使用[ResNet50预训练模型](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt)进行微调。调用`resnet50`构造ResNet50模型，并设置`pretrained`参数为True，将会自动下载ResNet50预训练模型，并加载预训练模型中的参数到网络中。然后定义优化器和损失函数，逐个epoch打印训练的损失值和评估精度，并保存评估精度最高的ckpt文件（resnet50-best.ckpt）到当前路径的./BestCheckPoint下。\n",
    "\n",
    "由于预训练模型全连接层（fc）的输出大小（对应参数`num_classes`）为1000， 为了成功加载预训练权重，我们将模型的全连接输出大小设置为默认的1000。CIFAR10数据集共有10个分类，在使用该数据集进行训练时，需要将加载好预训练权重的模型全连接层输出大小重置为10。\n",
    "\n",
    "> 此处我们展示了5个epochs的训练过程，如果想要达到理想的训练效果，建议训练80个epochs。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "9cf10c03",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt (97.7 MB)\n",
      "\n",
      "file_sizes: 100%|████████████████████████████| 102M/102M [00:01<00:00, 96.9MB/s]\n",
      "Successfully downloaded file to ./LoadPretrainedModel/resnet50_224_new.ckpt\n"
     ]
    }
   ],
   "source": [
    "# 定义ResNet50网络\n",
    "network = resnet50(pretrained=True)\n",
    "\n",
    "# 全连接层输入层的大小\n",
    "in_channel = network.fc.in_channels\n",
    "fc = nn.Dense(in_channels=in_channel, out_channels=10)\n",
    "# 重置全连接层\n",
    "network.fc = fc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "e1c632ff",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 设置学习率\n",
    "num_epochs = 5\n",
    "lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs,\n",
    "                        step_per_epoch=step_size_train, decay_epoch=num_epochs)\n",
    "# 定义优化器和损失函数\n",
    "opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)\n",
    "loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n",
    "\n",
    "\n",
    "def forward_fn(inputs, targets):\n",
    "    logits = network(inputs)\n",
    "    loss = loss_fn(logits, targets)\n",
    "    return loss\n",
    "\n",
    "\n",
    "grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters)\n",
    "\n",
    "\n",
    "def train_step(inputs, targets):\n",
    "    loss, grads = grad_fn(inputs, targets)\n",
    "    opt(grads)\n",
    "    return loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "b627e30c",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "# 创建迭代器\n",
    "data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs)\n",
    "data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs)\n",
    "\n",
    "# 最佳模型存储路径\n",
    "best_acc = 0\n",
    "best_ckpt_dir = \"./BestCheckpoint\"\n",
    "best_ckpt_path = \"./BestCheckpoint/resnet50-best.ckpt\"\n",
    "\n",
    "if not os.path.exists(best_ckpt_dir):\n",
    "    os.mkdir(best_ckpt_dir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "8a5170df",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import mindspore.ops as ops\n",
    "\n",
    "\n",
    "def train(data_loader, epoch):\n",
    "    \"\"\"模型训练\"\"\"\n",
    "    losses = []\n",
    "    network.set_train(True)\n",
    "\n",
    "    for i, (images, labels) in enumerate(data_loader):\n",
    "        loss = train_step(images, labels)\n",
    "        if i % 100 == 0 or i == step_size_train - 1:\n",
    "            print('Epoch: [%3d/%3d], Steps: [%3d/%3d], Train Loss: [%5.3f]' %\n",
    "                  (epoch + 1, num_epochs, i + 1, step_size_train, loss))\n",
    "        losses.append(loss)\n",
    "\n",
    "    return sum(losses) / len(losses)\n",
    "\n",
    "\n",
    "def evaluate(data_loader):\n",
    "    \"\"\"模型验证\"\"\"\n",
    "    network.set_train(False)\n",
    "\n",
    "    correct_num = 0.0  # 预测正确个数\n",
    "    total_num = 0.0  # 预测总数\n",
    "\n",
    "    for images, labels in data_loader:\n",
    "        logits = network(images)\n",
    "        pred = logits.argmax(axis=1)  # 预测结果\n",
    "        correct = ops.equal(pred, labels).reshape((-1, ))\n",
    "        correct_num += correct.sum().asnumpy()\n",
    "        total_num += correct.shape[0]\n",
    "\n",
    "    acc = correct_num / total_num  # 准确率\n",
    "\n",
    "    return acc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c1dec6cb7089fc5",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "# 开始循环训练\n",
    "print(\"Start Training Loop ...\")\n",
    "\n",
    "for epoch in range(num_epochs):\n",
    "    curr_loss = train(data_loader_train, epoch)\n",
    "    curr_acc = evaluate(data_loader_val)\n",
    "\n",
    "    print(\"-\" * 50)\n",
    "    print(\"Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]\" % (\n",
    "        epoch+1, num_epochs, curr_loss, curr_acc\n",
    "    ))\n",
    "    print(\"-\" * 50)\n",
    "\n",
    "    # 保存当前预测准确率最高的模型\n",
    "    if curr_acc > best_acc:\n",
    "        best_acc = curr_acc\n",
    "        ms.save_checkpoint(network, best_ckpt_path)\n",
    "\n",
    "print(\"=\" * 80)\n",
    "print(f\"End of validation the best Accuracy is: {best_acc: 5.3f}, \"\n",
    "      f\"save the best ckpt file in {best_ckpt_path}\", flush=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46e28f6f",
   "metadata": {},
   "source": [
    "## 可视化模型预测\n",
    "\n",
    "定义`visualize_model`函数，使用上述验证精度最高的模型对CIFAR-10测试数据集进行预测，并将预测结果可视化。若预测字体颜色为蓝色表示为预测正确，预测字体颜色为红色则表示预测错误。\n",
    "\n",
    "> 由上面的结果可知，5个epochs下模型在验证数据集的预测准确率在70%左右，即一般情况下，6张图片中会有2张预测失败。如果想要达到理想的训练效果，建议训练80个epochs。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "6ba2fa94",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 6 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "\n",
    "def visualize_model(best_ckpt_path, dataset_val):\n",
    "    num_class = 10  # 对狼和狗图像进行二分类\n",
    "    net = resnet50(num_class)\n",
    "    # 加载模型参数\n",
    "    param_dict = ms.load_checkpoint(best_ckpt_path)\n",
    "    ms.load_param_into_net(net, param_dict)\n",
    "    # 加载验证集的数据进行验证\n",
    "    data = next(dataset_val.create_dict_iterator())\n",
    "    images = data[\"image\"]\n",
    "    labels = data[\"label\"]\n",
    "    # 预测图像类别\n",
    "    output = net(data['image'])\n",
    "    pred = np.argmax(output.asnumpy(), axis=1)\n",
    "\n",
    "    # 图像分类\n",
    "    classes = []\n",
    "\n",
    "    with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n",
    "        for line in f:\n",
    "            line = line.rstrip()\n",
    "            if line:\n",
    "                classes.append(line)\n",
    "\n",
    "    # 显示图像及图像的预测值\n",
    "    plt.figure()\n",
    "    for i in range(6):\n",
    "        plt.subplot(2, 3, i + 1)\n",
    "        # 若预测正确，显示为蓝色；若预测错误，显示为红色\n",
    "        color = 'blue' if pred[i] == labels.asnumpy()[i] else 'red'\n",
    "        plt.title('predict:{}'.format(classes[pred[i]]), color=color)\n",
    "        picture_show = np.transpose(images.asnumpy()[i], (1, 2, 0))\n",
    "        mean = np.array([0.4914, 0.4822, 0.4465])\n",
    "        std = np.array([0.2023, 0.1994, 0.2010])\n",
    "        picture_show = std * picture_show + mean\n",
    "        picture_show = np.clip(picture_show, 0, 1)\n",
    "        plt.imshow(picture_show)\n",
    "        plt.axis('off')\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "# 使用测试数据集进行验证\n",
    "visualize_model(best_ckpt_path=best_ckpt_path, dataset_val=dataset_val)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.19"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
